/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.googlecode.datawander.codeexecutor;

import com.db4o.reflect.ReflectClass;
import com.googlecode.datawander.i18n.I18n;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import org.apache.log4j.Logger;

/**
 * Code loader use to load Persistence objects from JAR file or from directory
 * to read structure of object stored in object database. After read that
 * infomations, class have to be unload.
 * @author Marcin Stachniuk
 */
public class PersistenceCodeLoader extends ClassLoader {

    private static final Logger logger = Logger.getLogger(PersistenceCodeLoader.class);
    private static final String DB4OUTIL_KEY = "Db4oUtil";
    private static final String ORACLE_KEY = "OracleConnector";

    private String classDirectory;
    private Set<Class> classSet = new HashSet<Class>();
    private long fileLength;
    private Map<String, Class> databaseConnectors = new HashMap<String, Class>();
    private String db4oFilename;

    public String getClassOrJarDirectory() {
        return classDirectory;
    }

    public void setClassDirectory(String classOrJarDirectory) {
        this.classDirectory = classOrJarDirectory;
    }

    public Set<Class> getClassMap() {
        return classSet;
    }

    public Set<Class> reloadAllNeededClass(Set<String> neededClassNames) throws ClassNotFoundException {
        logger.trace("loadAllNeededClass()");

        if(classDirectory == null) {
            throw new NullPointerException(I18n.getString("mustsetClassOrJarDirectory"));
        }

        if(classSet == null) {
            classSet = new HashSet<Class>();
        }

        String message = null;

        for(String str : neededClassNames) {
            Class loadedClass = loadClass(str);
            classSet.add(loadedClass);
            if(loadedClass != null) {
                message = I18n.getString("loadedclass");
                message = String.format(message, loadedClass.getName());
                logger.debug(message);
            } else {
                message = I18n.getString("cantloadedclass");
                message = String.format(message, str);
                logger.debug(message);
                throw new ClassNotFoundException(message);
            }
        }

        logger.info(I18n.getString("loadedallclassessucess"));
        return classSet;
    }


    public Set<Class> loadAllNeededClass(Set<ReflectClass> needClassSet) throws ClassNotFoundException {
        logger.trace("loadAllNeededClass(Set size: " + needClassSet.size() + ")");

        if(classDirectory == null) {
            throw new NullPointerException(I18n.getString("mustsetClassOrJarDirectory"));
        }

        if(classSet == null) {
            classSet = new HashSet<Class>();
        }

        String message = null;

        for(ReflectClass rc : needClassSet) {
            Class loadedClass = loadClass(rc.getName());
            classSet.add(loadedClass);
            if(loadedClass != null) {
                message = I18n.getString("loadedclass");
                message = String.format(message, loadedClass.getName());
                logger.debug(message);
            } else {
                message = I18n.getString("cantloadedclass");
                message = String.format(message, rc.getName());
                logger.debug(message);
                throw new ClassNotFoundException(message);
            }
        }

        logger.info(I18n.getString("loadedallclassessucess"));
        return classSet;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        logger.trace("findClass("+name+")");

        try {
            InputStream in = openClassFile(name);

            byte classBytes[] = new byte[(int)fileLength];
            in.read(classBytes);
            in.close();

            logger.debug(I18n.getString("loadwholefile"));

            Class cl = defineClass(name, classBytes, 0, classBytes.length);
	    if (cl == null) {
                logger.error(I18n.getString("createdclassisnull"));
                throw new ClassNotFoundException(name);
            }
	    return cl;
        } catch (FileNotFoundException ex) {
            String message = I18n.getString("cantfindclass");
            message = String.format(message, name);
            logger.error(message, ex);
            throw new ClassNotFoundException(message);
        } catch (IOException ex) {
            String message = I18n.getString("cantreadclass");
            message = String.format(message, name);
            logger.error(message, ex);
            throw new ClassNotFoundException(message);
        }
    }

    /**
     * Open class file, and set size of class in fileLength field.
     * @param name Class name to load.
     * @return InputStream to read that *.class file
     */
    private  InputStream openClassFile(String name) throws FileNotFoundException, IOException {
        FileInputStream in = null;
        String fileName = null;
        File file = null;
        String message = null;
        fileName = classDirectory +
                (classDirectory.endsWith(File.separator) ? "" : File.separator)
                + name + ".class";

        file = new File(fileName);
        if(!file.exists()) {
            message = I18n.getString("filenotexist");
            message = String.format(message, fileName);
            logger.debug(message);

            fileName = classDirectory +
                (classDirectory.endsWith(File.separator) ? "" : File.separator)
                + name.replace(".", File.separator) + ".class";

            file = new File(fileName);
        }

        fileLength = file.length();

        in = new FileInputStream(file);
        message = I18n.getString("findclassfiletoload");
        message = String.format(message, fileName);
        logger.debug(message);
        
        return in;
    }

    public Object invokeLoadAllObjects(Class cl) throws CodeRunException {
        logger.trace("invokeLoadAllObjects("+cl.getName()+");");
        try {
            Method loadAllObject = cl.getDeclaredMethod("loadAllObjects");
            Object obj = loadAllObject.invoke(null);
            
            String message = I18n.getString("loadallobjectsbyclass");
            message = String.format(message, cl.getName());
            logger.info(message);
            return obj;
        } catch(NoSuchMethodException e) {
            throw new CodeRunException(e.getMessage());
        } catch(IllegalAccessException e) {
            throw new CodeRunException(e.getMessage());
        } catch (IllegalArgumentException e) {
            throw new CodeRunException(e.getMessage());
        } catch (InvocationTargetException e) {
            logger.error(I18n.getString("invokedmethodthrowexception"), e);
            throw new CodeRunException(e.getMessage());
        }
    }

    public void invokeSaveAllRecords(Class cl, Object obj) throws CodeRunException {
        logger.trace("invokeSaveAllRecords("+cl.getName()+", obj);");
        try {
            Method loadAllObject = cl.getDeclaredMethod("saveAllRecords", java.util.List.class);
            //List myList = (java.util.List)obj;
            loadAllObject.invoke(null, obj);

            String message = I18n.getString("saveallrecordsbyclass");
            message = String.format(message, cl.getName());
            logger.info(message);
            
        } catch(NoSuchMethodException e) {
            throw new CodeRunException(e.getMessage());
        } catch(IllegalAccessException e) {
            throw new CodeRunException(e.getMessage());
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
            throw new CodeRunException(e.getMessage());
        } catch (InvocationTargetException e) {
            logger.error(I18n.getString("invokedmethodthrowexception"), e);
            throw new CodeRunException(e.getMessage());
        }
    }

    /**
     * Set object database filename
     * @param filename Path to file where is object database
     * @throws CodeRunException When is error in operation
     */
    public void setDb4oFilename(String filename) throws CodeRunException {
        logger.trace("setDb4oFilename("+filename+");");

        db4oFilename = filename;

        Class cl = databaseConnectors.get("Db4oUtil");
        if(cl == null) {
            String message = I18n.getString("unabletoloaddb4outilclass");
            logger.error(message);
            throw new CodeRunException(message);
        }

        try {
            Method methodSetUrl = cl.getMethod("setDatabaseFilename", String.class);
            methodSetUrl.invoke(null, filename);
        } catch (Exception e) {
            String message = I18n.getString("exceptioninsetdb4ofilename");
            logger.error(message, e);
            throw new CodeRunException(message);
        }
    }

    /**
     * Invoke methods to set prametrs to connet do relational database
     * @param url ULR adres to connect to relational database.
     * @param username Database username.
     * @param password Password.
     * @throws CodeRunException When is error in operation
     */
    public void setRealtionalDatabaseParametrs(String url, String username, String password) throws CodeRunException {
        logger.trace("setRealtionalDatabaseParametrs("+url+", "+username+", "+password+");");

        Class cl = databaseConnectors.get("OracleConnector");
        if(cl == null) {
            String message = I18n.getString("unabletoloadoracleconnectiorclass");
            logger.error(message);
            throw new CodeRunException(message);
        }

        try {
            Method methodSetUrl = cl.getMethod("setUrl", String.class);
            methodSetUrl.invoke(null, url);

            Method methodSetUsername = cl.getMethod("setUsername", String.class);
            methodSetUsername.invoke(null, username);

            Method methodSetPassword = cl.getMethod("setPassword", String.class);
            methodSetPassword.invoke(null, password);

            logger.info(I18n.getString("setConectionParametrsWithDatabaseSuccess"));
        } catch(NoSuchMethodException e) {
            throw new CodeRunException(e.getMessage());
        } catch(IllegalAccessException e) {
            throw new CodeRunException(e.getMessage());
        } catch (IllegalArgumentException e) {
            throw new CodeRunException(e.getMessage());
        } catch (InvocationTargetException e) {
            logger.error(I18n.getString("invokedmethodthrowexception"), e);
            throw new CodeRunException(e.getMessage());
        }
    }

    public void loadConnectorsClasses(Set<String> connectorsSet) throws ClassNotFoundException {
        if(databaseConnectors == null) {
            databaseConnectors = new HashMap<String, Class>();
        }

        for(String str : connectorsSet) {
            Class loadedClass = loadClass(str);
            if(str.contains(DB4OUTIL_KEY)) {
                databaseConnectors.put(DB4OUTIL_KEY, loadedClass);
            } else if(str.contains("OracleConnector")) {
                databaseConnectors.put(ORACLE_KEY, loadedClass);
            } else {
                throw new RuntimeException(I18n.getString("badnamefordatabaseconnector"));
            }
            
            String message = null;
            if(loadedClass != null) {
                message = I18n.getString("loadedclass");
                message = String.format(message, loadedClass.getName());
                logger.debug(message);
            } else {
                message = I18n.getString("cantloadedclass");
                message = String.format(message, str);
                logger.debug(message);
                throw new ClassNotFoundException(message);
            }
        }
    }


}
